/** @file    NetworkManagerHelpers.h
 *  @author  Alessandro Polo
 *  @version $Id: NetworkManagerHelpers.h 2861 2010-08-07 02:42:53Z alex $
 *  @brief
 * 
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <core/NetworkManager.h>

 #include <core/Bus.h>
 #include <core/Thread.h>
 #include <core/MessageQueue.h>
 #include <core/WoshKernel.h>


namespace wosh {


/**
 * @class   wosh::NetworkOutMessageHandler
 * @brief   Helper class, manage outgoing message
 *
 * @author  Alessandro Polo
 ****************************************************************************/
class NetworkOutMessageHandler :	public ThreadImpl,
									public virtual IBusListener
 {
	WOSH_CLASSNAME(wosh::NetworkOutMessageHandler)
	WOSH_CLASS_ABSTRACT(false)


	public:
		NetworkOutMessageHandler( NetworkManager* Parent )
			: ThreadImpl(Parent, "NetworkManager.NetworkOutMessageHandler"), parent(Parent) { }
		virtual ~NetworkOutMessageHandler() { }

	public:
		virtual void runThread() {
			if ( this->parent == NULL ) return;

			while( this->running )
			 {
				ThreadImpl::sleepForMSec(50);
				setThreadAlive();
				if ( this->msgQueue.isEmpty() ) {
					ThreadImpl::sleepForMSec(100);
					continue;
				 }

				// get and remove it from queue
				Message* message = this->msgQueue.pop();
				if ( message == NULL ) continue;

				sendMessage(*message);
				delete message;
			 }
		 }

		virtual void busMessage( const Message& message, const Bus*  )
		 {
			if ( message.isEmpty() ) return;

			// message for local kernel, just ignore
			if ( message.getDestination().isKernelLocal() )
				return;

			if ( message.getDestinationBus() == _Bus_ANY ) {
				if ( message.getSourceBus() != message.getCurrentBus() )
					return;
			 }

			// kernel is undefined.. kinda an issue!
			if ( message.getDestination().isKernelEmpty() ) {
				this->parent->Log(LOG_WARNING, ":busMessage() : Empty Kernel of message %"PRId64"", message.getID() );
				return;
			 }

			Message* messageCopy = message.clone();

			// are we forwarding a message!? then add myself as forwarder
			if ( !messageCopy->getSource().isKernelLocal() ) {
				if ( !messageCopy->hasNetworkForwarders() )
					messageCopy->setNetworkForwarders( new MessageNetworkForwarding() );
				messageCopy->getNetworkForwarders()->addHost(WoshKernel::getKernelName());
			 }

			this->msgQueue.push_back( messageCopy );
		}

		virtual void sendMessage( const Message& message )
		 {
			if ( message.getDestination().isKernelAny() ) {
				// broadcast
				this->parent->Hosts.transactionBeginRead();
				NetworkManager::tStrWoshHostMap::ConstIterator it;
				const NetworkManager::tStrWoshHostMap::ConstIterator it_end = this->parent->Hosts.end();
				for ( it=this->parent->Hosts.begin(); it!=it_end; ++it ) {
					WoshHost* host = it->second;
					if ( host == NULL ) continue;
					// don't send to myself..
					if ( host == this->parent->localHost ) continue;
					// don't send to source kernel..
					if ( host->getName() == message.getSourceKernel() ) continue;
					// don't send to a previous forwarder..
					if ( message.hasNetworkForwarders() ) {
						if ( message.getNetworkForwarders()->getHostIndex(host->getName()) >= 0 )
							continue;
					 }
					if ( !host->isConnected() ) {
						continue;
					 }
					// host ok, send!
					sendMessagetoHost(message, host);
				 }
				this->parent->Hosts.transactionEnd();
				return;
			 }

			this->parent->Hosts.transactionBeginRead();
			WoshHost* host = this->parent->Hosts.find(message.getDestination().getKernelName());
			if ( host == NULL ) {
				// not found.. then check for static route
				this->parent->staticRoutes.transactionBeginRead();
				if ( this->parent->staticRoutes.exists(message.getDestination().getKernelName()) ) {
					std::string routerHostName = this->parent->staticRoutes.find(message.getDestination().getKernelName());
					host = this->parent->Hosts.find(routerHostName);
					this->parent->Log(LOG_VERBOSE, ":sendMessage(%"PRId64") : Static route for Host '%s' Found: %s", message.getID(), message.getDestination().getKernelName().c_str(), routerHostName.c_str() );
				 }
				else {
					this->parent->staticRoutes.transactionEnd();
					this->parent->Hosts.transactionEnd();
					this->parent->Log(LOG_CRITICAL, ":sendMessage(%"PRId64") : Host '%s' Not Found (and no static route found)!", message.getID(), message.getDestination().getKernelName().c_str() );
					return;
				 }
				this->parent->staticRoutes.transactionEnd();
			 }

			sendMessagetoHost(message, host);
			this->parent->Hosts.transactionEnd();
		 }

		void sendMessagetoHost( const Message& message, WoshHost* host )
		 {
			if ( host == NULL ) return;

			std::vector<NetworkChannel*> chs;
			this->parent->Channels.transactionBeginRead();

			WRESULT ret = this->parent->getChannelsOf_(chs, host, true); // no mux
			if ( WFAILED(ret) || chs.size() == 0 ) {
				this->parent->Channels.transactionEnd();
				this->parent->Log(LOG_WARNING, ":sendMessagetoHost(%"PRId64") : Host '%s' Error/No Channels to TX?", message.getID(), host->getName().c_str() );
				return;
			 }

			NetworkChannel* ch = NULL;
			if (chs.size() == 1) {
				ch = chs.front();
				this->parent->Log(LOG_VERBOSE, ":sendMessagetoHost(%"PRId64") : Routing to Host '%s' using '%s' ret#%d", message.getID(), host->getName().c_str(), (*(chs.begin()))->getName().c_str(), ret );
			 }
			else {
				ch = chs.back();
				this->parent->Log(LOG_WARNING, ":sendMessagetoHost(%"PRId64") : Host '%s' More Channels (%d) to TX!? [selecting LAST]", message.getID(), host->getName().c_str(), chs.size() );
			 }

			if (ch != NULL) {
				ret = ch->sendMessage(&message);
				if ( WFAILED(ret) )
					this->parent->Log(LOG_WARNING, ":sendMessagetoHost(%"PRId64") : FAILED#%d Can't TX! [%s]", message.getID(), ret, host->getName().c_str() );
			 }
			else
				this->parent->Log(LOG_WARNING, ":sendMessagetoHost(%"PRId64") : FAILED. Can't select channel to TX! [%s]", message.getID(), host->getName().c_str() );

			this->parent->Channels.transactionEnd();
		 }

	private:
		NetworkManager* parent;
		MessageQueue msgQueue;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * @class   wosh::NetworkInMessageHandler
 * @brief   Helper class, manage incoming message
 *
 * @author  Alessandro Polo
 ****************************************************************************/
class NetworkInMessageHandler : public ThreadImpl
 {
	WOSH_CLASSNAME(wosh::NetworkInMessageHandler)
	WOSH_CLASS_ABSTRACT(false)

	public:
		NetworkInMessageHandler( NetworkManager* Parent )
			: ThreadImpl(Parent, "NetworkManager.NetworkInMessageHandler"), parent(Parent) { }
		virtual ~NetworkInMessageHandler() { }

	public:
		virtual void runThread() {
			if ( this->parent == NULL ) return;

			while( this->running )
			 {
				ThreadImpl::sleepForMSec(50);
				setThreadAlive();
				if ( this->msgQueue.isEmpty() ) {
					ThreadImpl::sleepForMSec(100);
					continue;
				 }

				// get and remove it from queue
				Message* message = this->msgQueue.pop();
				if ( message == NULL ) continue;
				this->parent->push_to_BusManager(message, NULL);
			 }
		 }

		virtual void pushMessage( Message* message )
		 {
			if ( message == NULL ) return;
			this->msgQueue.push_back( message );
		 }


	private:
		NetworkManager* parent;
		MessageQueue msgQueue;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * @class   wosh::NetworkManagerLooper
 * @brief   Helper class, check for dead/timedout channels
 *
 * @author  Alessandro Polo
 ****************************************************************************/
class NetworkManagerLooper : public ThreadImpl
 {
	WOSH_CLASSNAME(wosh::NetworkManagerLooper)
	WOSH_CLASS_ABSTRACT(false)

	public:
		NetworkManagerLooper( NetworkManager* Parent )
			: ThreadImpl(Parent, "NetworkManager.NetworkManagerLooper"), parent(Parent) { }
		virtual ~NetworkManagerLooper() { }

	public:
		virtual void runThread() {
			if ( this->parent == NULL ) return;

			unsigned int counter = 0;
			while(this->running) {
				ThreadImpl::sleepForMSec(500);
				setThreadAlive();
				if ( ++counter < 10 )
					continue;
				counter = 0;

				this->parent->Hosts.transactionBeginWrite();
				NetworkManager::tStrWoshHostMap::Iterator it_h;
				const NetworkManager::tStrWoshHostMap::ConstIterator it_h_end = this->parent->Hosts.end();
				for ( it_h=this->parent->Hosts.begin(); it_h!=it_h_end; ++it_h ) {
					WoshHost* host = it_h->second;
					if ( !this->running ) break;
					if ( host == NULL || host == this->parent->localHost ) continue;
					if ( !host->isTimedOut( this->parent->hostTimeout ) ) continue;
					this->parent->Log(LOG_INFO, " Host '%s' Timed Out! [seen at %s]", host->getName().c_str(), Utilities::getTimeStampUTF(host->getSeen()).c_str() );
					this->parent->removeHost_( host );
					break;
				 }
				this->parent->Hosts.transactionEnd();

				
				this->parent->Channels.transactionBeginWrite();
				NetworkManager::tLongNetworkChannelMap::ConstIterator it;
				const NetworkManager::tLongNetworkChannelMap::ConstIterator it_end = this->parent->Channels.end();
				for ( it=this->parent->Channels.begin(); it!=it_end; ++it ) {
					if ( it->second == NULL || !it->second->getDeleteFlag() ) continue;
					this->parent->Log(LOG_DEBUG, " Channel '%s' DeleteFlag on!", it->second->getName().c_str() );
					this->parent->removeChannel_( it->second );
					break;
				 }
				this->parent->Channels.transactionEnd();

			 }

		 }

	private:
		NetworkManager* parent;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
